package com.gitee.l0km.beanfilter.core.json;

import static com.gitee.l0km.beanfilter.core.utils.CaseSupport.isSnakecase;
import static com.gitee.l0km.beanfilter.core.utils.CaseSupport.toCamelcase;

import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
import com.fasterxml.jackson.databind.introspect.BasicBeanDescription;
import com.gitee.l0km.aocache.annotations.AoWeakCacheable;
import com.gitee.l0km.beanfilter.core.CodecPhase;
import com.gitee.l0km.beanfilter.core.CodecScene;
import com.gitee.l0km.beanfilter.core.Filterable;
import com.gitee.l0km.beanfilter.core.IFilter;
import com.gitee.l0km.beanfilter.core.LocalSupplyFilter;
import com.gitee.l0km.beanfilter.core.RequestPhase;
import com.gitee.l0km.beanfilter.core.SupplyFilter;
import com.gitee.l0km.beanfilter.core.context.LocalContext;
import com.google.common.base.Supplier;

/**
 * dynamic field filter implementation of Jackson {@link JsonDeserializer},<br>
 * MAVEN dependencies:
 * <pre>
 *		&lt;dependency&gt;
 *			&lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
 *			&lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
 *			&lt;version&gt;${jackson.version}&lt;/version&gt;
 *		&lt;/dependency&gt;
 * </pre>
 * 
 * @author guyadong
 */
public class JacksonFilterableDeserializer extends BeanDeserializer implements Filterable{
	private static final long serialVersionUID = 7410414787512241455L;
	private final boolean autoCase;
	private final LocalSupplyFilter localSupplyFilter;
	public JacksonFilterableDeserializer(Class<?> beanClass, boolean ignoreAllUnknown, boolean autoCase) {
		super(createBeanDeserializer(beanClass),true);
		this.autoCase = autoCase;
		this.localSupplyFilter = new LocalSupplyFilter(beanClass, RequestPhase.DESERIALIZING, CodecScene.JACKSON);
	}
	public JacksonFilterableDeserializer(Class<?> beanClass) {
		this(beanClass, true, false);
	}
	protected SupplyFilter getSupplyFilter() {
		return localSupplyFilter.getSupplyFilter();
	}
	@Override
	public void injectFilter(Supplier<? extends IFilter> fieldFilterSupplier, Supplier<? extends IFilter> valueFilterSupplier, CodecPhase codecPhase){
		if(null != fieldFilterSupplier || null !=valueFilterSupplier) {
			SupplyFilter supplyFilter = SupplyFilter.combine2(fieldFilterSupplier,valueFilterSupplier);
			localSupplyFilter.setSupplyFilter(supplyFilter);
		}
	}
	@Override
	public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
		try {
			LocalContext.enteringEnv(null, RequestPhase.DESERIALIZING, CodecScene.JACKSON);
			return doDeserialize(jp, ctxt);
		} finally {
			LocalContext.exitingEnv();
		}
	}
	protected Object doDeserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
		// see also BeanDeserializer.vanillaDeserialize
		Object bean = _valueInstantiator.createUsingDefault(ctxt);
		
		/** 输入中存在的字段 */
		LinkedHashSet<String> deserialized = new LinkedHashSet<>();
		/** 值过滤时被额外添加非null值的字段 */
		LinkedHashSet<String> modifiedSet = new LinkedHashSet<>();
		for(String propName = jp.nextFieldName();propName != null;propName = jp.nextFieldName()) {
			jp.nextToken();
			SettableBeanProperty prop = findProperty(this,propName);
			if (prop != null) { // normal case
				deserialized.add(prop.getName());
				try {
					deserializeProperty(jp, ctxt, bean, prop);
				} catch (Exception e) {
					wrapAndThrow(e, bean, propName, ctxt);
				}
				continue;
			}
			handleUnknownVanilla(jp, ctxt, bean, propName);
		}
		return processUndeserialized(bean,deserialized, modifiedSet);
	}
	/**
	 * 对反序列化时不存在的参数字段进行值过滤
	 * @param bean
	 * @param deserialized 输入中存在的字段
	 * @param additional [out]保存不存在的参数字段进行值过滤返回为非{@code null}的值的字段
	 * @return always bean
	 * @throws IOException
	 */
	protected <T> T processUndeserialized(T bean,Set<String> deserialized, Set<String> additional) throws IOException {
		for(Iterator<SettableBeanProperty> itor = properties();itor.hasNext();) {
			SettableBeanProperty prop = itor.next();
			if(!deserialized.contains(prop.getName())) {
				if(getSupplyFilter().permit(handledType(), prop.getName())) {
					if(process(handledType(),bean,prop,null, true)) {
						additional.add(prop.getName());
					}
				}
			}
		}
		return bean;
	}
	/**
	 * 执行值过滤，过滤后的值与原值不同返回{@code true}，否则返回{@code false}
	 */
	private boolean process(Class<?> beanClass,
			Object bean,
			SettableBeanProperty prop,
			Object value, boolean ignoreNull) throws IOException {
		Object processed = getSupplyFilter().process(beanClass, prop.getName(), value);
		if( ignoreNull && null == value && null == processed) {
			return false;
		}
		if(null != processed && !prop.getType().isTypeOrSuperTypeOf(processed.getClass())) {
			/** 确保 IValueFilter 返回类型与字段类型匹配 */
			processed = convertValue(processed, prop.getType());
		}
		if(null != processed || !NullsConstantProvider.isSkipper(prop.getNullValueProvider())) {
			prop.set(bean, processed);
			return true;
		}
		return false;
	}
	protected boolean deserializeProperty(JsonParser jp, DeserializationContext ctxt,Object bean,SettableBeanProperty prop) throws IOException {
		if(getSupplyFilter().permit(handledType(), prop.getName())) {
			Object value = prop.deserialize(jp, ctxt);
			process(handledType(),bean,prop,value, false);
			return true;
		}else {
			jp.skipChildren();
			return false;
		}		
	}
	/**
	 * 查找字段名对应的{@link SettableBeanProperty}对象如果没找到且字段名为snake-case尝试转为camel-case查找。
	 * @param beanDeserializer
	 * @param propName
	 * @see BeanDeserializer#findProperty(String)
	 */
	protected SettableBeanProperty findProperty(BeanDeserializer beanDeserializer,String propName) {
		SettableBeanProperty prop = beanDeserializer.findProperty(propName);
		if( autoCase && null == prop && isSnakecase(propName)) {
			prop = beanDeserializer.findProperty(toCamelcase(propName));
		}
		return prop;
	}
	@AoWeakCacheable(maximumSize = 200, expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	private static Object convertValue(Object fromValue, JavaType toValueType) {
		/** 确保 IValueFilter 返回类型与字段类型匹配 */
		ObjectMapper mapper = new ObjectMapper();
		return mapper.convertValue(fromValue, toValueType);
	}
	/**
	 * 创建{@code beanClass}对应的{@link BeanDeserializerBase}实例用于父类构造方法的参数,
	 * 将{@code beanClass}的序列化参数注入到当前实例中
	 * @param beanClass
	 */
	private static BeanDeserializer createBeanDeserializer(Class<?> beanClass){
		try {
			ObjectMapper mapper = new ObjectMapper();
			DefaultDeserializationContext defctx = (DefaultDeserializationContext) mapper.getDeserializationContext();
			DefaultDeserializationContext ctxt = defctx.createInstance(mapper.getDeserializationConfig(),	null,null);
			JavaType type = ctxt.constructType(beanClass);
			BasicBeanDescription beanDesc = (BasicBeanDescription)ctxt.getConfig().introspect(type);
			BeanDeserializerFactory factory = (BeanDeserializerFactory) ctxt.getFactory();
			BeanDeserializer beanDeserializer = (BeanDeserializer) factory.buildBeanDeserializer(ctxt, type, beanDesc);
			beanDeserializer.resolve(ctxt);
			return beanDeserializer;
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}
}
